package com.opages.mvc.adapter.common.condition;

import com.opages.mvc.adapter.common.condition.annotation.ConditionalOnClass;
import com.opages.mvc.adapter.common.autoconfigure.AutoConfigurationMetadata;
import com.opages.mvc.adapter.common.condition.core.ConditionOutcome;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Order(Ordered.HIGHEST_PRECEDENCE)
public class OnClassCondition extends FilteringCondition{
    @Override
    protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        if (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {
            return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
        }else {
            OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0, autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
            return outcomesResolver.resolveOutcomes();
        }
    }

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
        if (onClasses != null) {
            List<String> missing = filter(onClasses, ClassNameFilter.MISSING, context.getClassLoader());
            if (!missing.isEmpty()) {
                return ConditionOutcome.noMatch();
            }
        }
        return ConditionOutcome.match();
    }

    private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {
        MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
        if (attributes == null) {
            return null;
        }
        List<String> candidates = new ArrayList<>();
        addAll(candidates, attributes.get("value"));
        addAll(candidates, attributes.get("name"));
        return candidates;
    }

    private void addAll(List<String> list, List<Object> itemsToAdd) {
        if (itemsToAdd != null) {
            for (Object item : itemsToAdd) {
                Collections.addAll(list, (String[]) item);
            }
        }
    }

    private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
                                                       AutoConfigurationMetadata autoConfigurationMetadata) {
        int split = autoConfigurationClasses.length / 2;
        OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
                autoConfigurationMetadata);
        OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
                autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
        ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
        ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
        ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
        System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
        System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
        return outcomes;
    }

    private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end,
                                                    AutoConfigurationMetadata autoConfigurationMetadata) {
        OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, start, end,
                autoConfigurationMetadata, getBeanClassLoader());
        try {
            return new ThreadedOutcomesResolver(outcomesResolver);
        }
        catch (AccessControlException ex) {
            return outcomesResolver;
        }
    }

    //结果解析接口
    private interface OutcomesResolver {
        //结果解析方法
        ConditionOutcome[] resolveOutcomes();
    }
    //结果解析多线程处理器
    private static final class ThreadedOutcomesResolver implements OutcomesResolver {
        private final Thread thread;
        private volatile ConditionOutcome[] outcomes;
        //使用线程处理结果解析
        private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {
            this.thread = new Thread(() -> this.outcomes = outcomesResolver.resolveOutcomes());
            this.thread.start();
        }
        @Override
        public ConditionOutcome[] resolveOutcomes() {
            try {
                this.thread.join();
            }
            catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
            return this.outcomes;
        }

    }
    //结果解析实现类
    private static final class StandardOutcomesResolver implements OutcomesResolver {
        private final String[] autoConfigurationClasses;
        private final int start;
        private final int end;
        private final AutoConfigurationMetadata autoConfigurationMetadata;
        private final ClassLoader beanClassLoader;
        private StandardOutcomesResolver(String[] autoConfigurationClasses, int start, int end,
                                         AutoConfigurationMetadata autoConfigurationMetadata, ClassLoader beanClassLoader) {
            this.autoConfigurationClasses = autoConfigurationClasses;
            this.start = start;
            this.end = end;
            this.autoConfigurationMetadata = autoConfigurationMetadata;
            this.beanClassLoader = beanClassLoader;
        }
        @Override
        public ConditionOutcome[] resolveOutcomes() {
            return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
        }
        private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
                                               AutoConfigurationMetadata autoConfigurationMetadata) {
            ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
            for (int i = start; i < end; i++) {
                String autoConfigurationClass = autoConfigurationClasses[i];
                if (autoConfigurationClass != null) {
                    //自动配置类.ConditionalOnClass=条件
                    String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
                    if (candidates != null) {
                        //如果存在条件再进行getOutcome获取,再判断在容器中是否存在Class
                        outcomes[i - start] = getOutcome(candidates);
                    }
                }
            }
            return outcomes;
        }
        private ConditionOutcome getOutcome(String candidates) {
            try {
                if (!candidates.contains(",")) {
                    return getOutcome(candidates, this.beanClassLoader);
                }
                for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
                    ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
                    if (outcome != null) {
                        return outcome;
                    }
                }
            }
            catch (Exception ex) {
                // We'll get another chance later
            }
            return null;
        }
        private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
            if (ClassNameFilter.MISSING.matches(className, classLoader)) {
                return ConditionOutcome.noMatch();
            }
            return null;
        }
    }
}
